from PIL import Image
import io
from typing import Optional, Dict, Any
from openai import OpenAI
import os
import base64
from dotenv import load_dotenv
import json

load_dotenv()

prompt = """
### ✅ **专业级智能体提示词（Prompt）设计**

你是一个专业的多模态图像分析智能体，具备面部识别、美学评估与语义理解能力。请根据用户上传的图片，执行以下分析流程：

### 分析步骤：
1. 判断图像主体是“人类”还是“动物”。
2. 若主体为人类，执行【人类分析流程】；若为动物，执行【动物分析流程】；若图像模糊、无清晰面部或非生物主体，返回错误说明。

---

### 【人类分析流程】
- **beauty_score**: 基于面部对称性、五官比例、皮肤状态、整体协调性等维度，给出50-100之间的整数评分（50为基准，100为极致吸引力）。
- **gender**: 判断性别倾向，输出 "male" 或 "female"；若中性或难以判断，可输出 "neutral" 或 "unknown"。
- **age_range**: 输出年龄范围，格式如 "20-25"、"30左右"、"40以上" 等中文描述。
- **tags**: 提供3-5个描述性中文标签，如“帅气”、“清秀”、“知性”、“阳光”、“成熟”等。

### 【动物分析流程】
- **beauty_score**: 基于外貌吸引力、毛发状态、眼神、姿态等，给出50-100之间的整数评分。
- **tags**: 提供3-5个描述性中文标签，如“可爱”、“呆萌”、“漂亮”、“威武”、“憨态可掬”等。

---

### 输出要求：
- 必须以 **JSON 格式** 返回结果。
- 包含字段：`subject_type`（"human" 或 "animal"）、`analysis`（分析内容对象）、`confidence`（置信度，高/中/低）、`error`（仅在无法分析时存在）。
- 所有判断必须基于图像视觉信息，不得猜测身份或背景。
- 若图像质量差、无面部、或非目标对象，设置 `subject_type: "unknown"` 并填写 `error` 说明原因。

请开始分析这张图片：

---

### ✅ **示例返回（人类）—— JSON 格式**
```json
{
  "subject_type": "human",
  "confidence": "high",
  "analysis": {
    "beauty_score": 88,
    "gender": "female",
    "age_range": "25-30",
    "tags": ["清秀", "知性", "气质优雅", "五官立体", "皮肤细腻"]
  }
}
```
---

### ✅ **示例返回（动物）—— JSON 格式**
```json
{
  "subject_type": "animal",
  "confidence": "high",
  "analysis": {
    "beauty_score": 91,
    "tags": ["可爱", "呆萌", "毛发蓬松", "眼神灵动", "惹人喜爱"]
  }
}
```

---

### ✅ **示例返回（无法分析）—— JSON 格式**
```json
  "subject_type": "unknown",
  "confidence": "low",
  "error": "图像模糊，面部不清晰，无法进行有效分析。",
  "analysis": null
```json


"""


class LLMClient:
    """LLM客户端封装类，支持图像理解功能"""

    def __init__(self, api_key: str = None,
                 base_url: str = "https://open.bigmodel.cn/api/paas/v4/",
                 model: str = None):
        """
        初始化LLM客户端
        
        Args:
            api_key: API密钥
            base_url: API基础URL，默认为智谱AI
        """

        if api_key is None:
            api_key = os.getenv("OPENAI_API_KEY")

        if model is None:
            model = os.getenv("OPENAI_MODEL")

        self.model = model
        self.client = OpenAI(
            api_key=api_key,
            base_url=base_url
        )

    @staticmethod
    def encode_image(image_path: str) -> str:
        """
        将图像文件编码为base64字符串
        
        Args:
            image_path: 图像文件路径
            
        Returns:
            base64编码的图像字符串
            
        Raises:
            FileNotFoundError: 当图像文件不存在时
            IOError: 当读取文件失败时
        """
        try:
            with open(image_path, "rb") as image_file:
                return base64.b64encode(image_file.read()).decode('utf-8')
        except FileNotFoundError:
            raise FileNotFoundError(f"图像文件未找到: {image_path}")
        except IOError as e:
            raise IOError(f"读取图像文件失败: {e}")

    @staticmethod
    def encode_image_from_pil(image: Image.Image, format: str = "JPEG") -> str:
        """
        将PIL图像对象编码为base64字符串
        
        Args:
            image: PIL图像对象
            format: 图像格式，默认为JPEG
            
        Returns:
            base64编码的图像字符串
        """
        buffer = io.BytesIO()
        image.save(buffer, format=format)
        buffer.seek(0)
        return base64.b64encode(buffer.read()).decode('utf-8')

    def analyze_image(
            self,
            image_path: Optional[str] = None,
            image_base64: Optional[str] = None,
            prompt: str = "请描述这张图片的内容",
            temperature: float = 0.7,
            max_tokens: Optional[int] = None
    ) -> str:
        """
        分析图像内容
        
        Args:
            image_path: 图像文件路径（与image_base64二选一）
            image_base64: base64编码的图像字符串（与image_path二选一）
            prompt: 分析提示词
            model: 模型名称，默认为"glm-4v"
            temperature: 温度参数，控制生成多样性
            max_tokens: 最大token数限制
            
        Returns:
            模型返回的图像描述文本
            
        Raises:
            ValueError: 当未提供图像或参数无效时
            Exception: 当API调用失败时
        """
        if not image_path and not image_base64:
            raise ValueError("必须提供image_path或image_base64参数")

        # 获取base64编码的图像
        if image_path:
            image_base64 = self.encode_image(image_path)

        try:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[
                    {"role": "system", "content": prompt},
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "text",
                                "text": prompt
                            },
                            {
                                "type": "image_url",
                                "image_url": {
                                    "url": f"data:image/jpeg;base64,{image_base64}"
                                }
                            }
                        ]
                    }
                ],
                temperature=temperature,
                response_format={"type": "json_object"},
                **({"max_tokens": max_tokens} if max_tokens else {})
            )

            content = json.loads(response.choices[0].message.content)

            return content

        except Exception as e:
            raise Exception(f"图像分析失败: {str(e)}")


# 使用示例
if __name__ == "__main__":
    # 初始化客户端
    client = LLMClient()

    # 单图分析示例
    try:
        result = client.analyze_image(
            image_path="C:/Users/Administrator/Desktop/微信图片_2025-08-04_180625_227.png",
            prompt=prompt
        )
        print("分析结果:", result)
    except Exception as e:
        print("错误:", e)
